package com.hwtx.form.domain;

import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Maps;
import com.hwtx.form.annotation.Action;
import com.hwtx.form.annotation.FormType;
import com.hwtx.form.domain.def.FormDef;
import com.hwtx.form.domain.def.FormProperties;
import com.hwtx.form.domain.def.FormValidateDef;
import com.hwtx.form.domain.def.Select;
import com.hwtx.form.domain.ds.DataSourceService;
import com.hwtx.form.domain.handle.FormDataHandle;
import com.hwtx.form.domain.handle.FormDefHandle;
import com.hwtx.form.domain.handle.ResourceFormHandleAction;
import com.hwtx.form.domain.handle.i18n.I18nConfig;
import com.hwtx.form.domain.listener.DynamicChangeFormHandleEvent;
import com.hwtx.form.domain.repo.FormRepo;
import com.hwtx.form.domain.service.FormService;
import com.hwtx.form.domain.service.ResourceService;
import com.hwtx.form.domain.vo.FormData;
import com.hwtx.form.domain.vo.FormListVo;
import com.hwtx.form.domain.vo.FormRepData;
import com.hwtx.form.domain.vo.ResultVo;
import com.hwtx.form.dto.*;
import com.hwtx.form.query.FormValueQuery;
import com.hwtx.form.util.FormUtil;
import io.geekidea.boot.config.properties.BootProperties;
import io.geekidea.boot.framework.exception.BusinessException;
import io.geekidea.boot.framework.page.Paging;
import io.geekidea.boot.system.entity.SysMenu;
import io.geekidea.boot.system.vo.AmisTableData;
import jakarta.annotation.Resource;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.stream.Streams;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Service;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.hwtx.form.domain.FormConstants.*;
import static com.hwtx.form.util.FormUtil.getStrPostValue;
import static com.hwtx.form.util.FormUtil.getUrlContext;
import static com.hwtx.form.util.JacksonUtil.json2Object;
import static com.hwtx.form.util.JacksonUtil.object2json;
import static io.geekidea.boot.common.constant.SystemConstant.I18N_CONFIG_FILE;

@Service
@Slf4j
public class FormServiceImpl implements FormService, ApplicationListener<ContextRefreshedEvent> {

    private final Cache<String, Method> actionMethodCache = CacheBuilder.newBuilder().expireAfterAccess(Duration.ofMinutes(10)).build();
    @Resource
    FormRepo formRepo;
    @Resource
    BootProperties bootProperties;
    @Resource
    private ServerProperties serverProperties;
    @Resource
    private I18nConfig i18nConfig;
    @Getter
    private Map<String, FormDataHandle> formHandles;
    private Map<Long, FormDefHandle> formDefHandles;

    @Override
    public void onApplicationEvent(@NotNull ContextRefreshedEvent event) {
        formHandles = FormUtil.getFromHandles();
        formDefHandles = FormUtil.getFormDefHandles();
    }

    @EventListener
    public void addFormHandle(DynamicChangeFormHandleEvent event) {
        if (event.isCreate()) {
            formHandles.putAll(event.getHandles());
        } else {
            event.getHandles().forEach((name, handle) -> {
                formHandles.remove(name);
                Streams.of(handle.getClass().getMethods()).forEach(method -> {
                    Action annotation = AnnotationUtils.findAnnotation(method, Action.class);
                    if (annotation != null) {
                        String key = annotation.value();
                        if (StringUtils.isBlank(annotation.value())) {
                            key = method.getName();
                        }
                        actionMethodCache.invalidate(key);
                    }
                });
            });
        }
    }

    @Override
    public String getStrFormDef(Long formId, Map<String, String[]> parameterMap) {
        FormDef formDef = getFormDef(formId);
        if (formDef != null) {
            FormDefHandle formDefHandle = formDefHandles.get(DEFAULT_FORM_ID);
            JsonNode root = formDef.getJsonRoot();
            formDefHandle.handleDefContent(formDef, root, parameterMap);

            formDefHandle = formDefHandles.get(formId);
            if (formDefHandle != null) {
                formDefHandle.handleDefContent(formDef, root, parameterMap);
            }
            return root.toString();
        }
        return "";
    }

    @Override
    public String getPageFormDef(String pageName, Map<String, String[]> parameters) {
        return getStrFormDef(formRepo.getFormIdByName(pageName), parameters);
    }

    @Override
    public Long getFormIdByName(String pageName) {
        return formRepo.getFormIdByName(pageName);
    }

    @Override
    public FormDef getFormDef(Long formId) {
        return formRepo.getFormDef(formId);
    }

    @Override
    public Map<String, String> validateForm(Long formId, Map<String, Object> formValues) {

        FormDef formDef = getFormDef(formId);
        if (formDef == null) {
            throw new BusinessException("表单定义不存在");
        }

        Map<String, String> validationResultMap = new HashMap<>();

        formValues.entrySet().stream().filter(entry -> !Objects.equals(entry.getKey(), FORM_ID)).forEach(entry -> {
            String key = entry.getKey();
            Object value = entry.getValue();
            FormValidateDef.ValidationResult validationResult = formDef.validateForm(key, value);
            if (!validationResult.isPass()) {
                validationResultMap.put(validationResult.getKey(), validationResult.getMessage());
            }
        });
        return validationResultMap;
    }

    @Override
    public FormRepData getFormData(FormValueQuery formValueQuery) throws Exception {
        FormDef formDef = getFormDef(formValueQuery.getFormId());
        Long formId = formValueQuery.getFormId();
        if (Objects.equals(formValueQuery.getType(), FORM_MANAGE_HANDLE_NAME) && !formDef.getName().contains("i18n")) {
            Long valueId = formValueQuery.getValueIds().getFirst();
            Map<String, Object> content = Maps.newHashMap();
            Locale locale = Locale.getDefault();
            if (StringUtils.isNotEmpty(formValueQuery.getLocale())) {
                locale = Locale.forLanguageTag(formValueQuery.getLocale());
            }
            content.put("content", i18nConfig.handlePageI18n(formRepo.getFormRawContentById(valueId), locale));
            content.put(FORM_ID, valueId);
            content.put("formName", formDef.getName());
            content.put("formType", formDef.getType());
            return FormRepData.builder().data(content).build();
        }
        FormDataHandle formDataHandle = getFormHandle(formDef, formId);
        return (FormRepData) ((DsInvoke) () -> formDataHandle.getFormData(formDef, formValueQuery)).invoke1(formValueQuery.getParameters());
    }

    @Override
    public Object remove(FormValueQuery formValueQuery) throws Exception {
        FormDef formDef = getFormDef(formValueQuery.getFormId());
        Object ret;
        if (Objects.equals(formValueQuery.getType(), FORM_MANAGE_HANDLE_NAME)) {
            ret = formRepo.deleteFormDef(formValueQuery.getValueIds().getFirst());
            if (!formDef.isConfig()) {
                return ret;
            }
        }
        String[] handle = formValueQuery.getParameters().get("handle");
        FormDataHandle formDataHandle;
        if (handle != null && handle.length > 0) {
            formDataHandle = getFormHandle(handle[0]);
        } else {
            formDataHandle = getFormHandle(formDef, formValueQuery.getFormId());
        }
        return formDataHandle.removeFormData(formDef, formValueQuery);
    }

    @Override
    public Object saveFormData(Long formId, Map<String, Object> param, String user) throws Exception {
        if (Objects.equals(param.get("type"), FORM_MANAGE_HANDLE_NAME)) {
            String strId = getStrPostValue(param, "id");
            FormDefDto formDefDto = new FormDefDto();
            String content = getStrPostValue(param, "content");
            if (StringUtils.isNotBlank(content)) {
                int index = content.indexOf("genBy\"");
                if (index > 0) {
                    int start = content.indexOf("\"", index + 7);
                    int end = content.indexOf("\"", start + 1);
                    if (start > 0 && end > 0) {
                        content = content.substring(0, start + 1) + strId + content.substring(end);
                    }
                }
                formDefDto.setContent(content);
            }
            String formName = getStrPostValue(param, "formName");
            if (StringUtils.isNotBlank(formName)) {
                formDefDto.setName(formName);
            }
            String formType = getStrPostValue(param, "formType");
            if (StringUtils.isNotBlank(formType)) {
                formDefDto.setType(formType);
            }
            if (StringUtils.isBlank(strId)) {
                return formRepo.addFormDef(formDefDto) > 0;
            }
            formDefDto.setId(Long.valueOf(strId));
            return formRepo.updateFormDef(formDefDto);
        }
        FormDef formDef = getFormDef(formId);
        FormDataHandle formDataHandle = getFormHandle(formDef, formId);
        FormData formData = FormData.builder().data(param).user(user).build();
        return formDataHandle.saveFormData(formDef, formData);
    }

    @Override
    public Object action(String formType, String actionName, Map<String, Object> content) throws Throwable {
        if (Objects.equals(formType, FORM_MANAGE_HANDLE_NAME)) {
            if (Objects.equals(actionName, "genConfig")) {
                return gen(content);
            }
            throw new BusinessException("不支持的操作,actionName = " + actionName);
        }
        FormDataHandle formDataHandle = getFormHandle(formType);
        Object targetAction = null;
        Method[] methods = formDataHandle.getClass().getMethods();
        FormType type = formDataHandle.getClass().getAnnotation(FormType.class);
        if (!type.actionClass().equals(void.class)) {
            methods = type.actionClass().getMethods();
            targetAction = SpringUtil.getBean(type.actionClass());
        }
        Method[] actionMethods = methods;
        Method action = actionMethodCache.get(actionName, () -> Streams.of(actionMethods).filter(method -> {
            Action annotation = AnnotationUtils.findAnnotation(method, Action.class);
            if (annotation == null) {
                return false;
            }
            String name = annotation.value();
            if (StringUtils.isBlank(annotation.value())) {
                name = method.getName();
            }
            boolean first = Objects.equals(name, actionName);
            if (method.getParameters().length > 0) {
                return first && method.getParameters()[0].getType().equals(Map.class);
            }
            return first;
        }).findFirst().orElse(null));
        Object finalTargetAction = targetAction;
        if (action.getParameters().length > 0) {
            if (targetAction != null) {
                return ((DsInvoke) () -> action.invoke(finalTargetAction, content)).invoke(content);
            }
            return ((DsInvoke) () -> action.invoke(formDataHandle, content)).invoke(content);
        }
        return ((DsInvoke) () -> action.invoke(Objects.requireNonNullElse(finalTargetAction, formDataHandle))).invoke(content);
    }

    private ResultVo gen(Map<String, Object> formData) {
        String msg = "生成表单配置数据成功";
        try {
            String localeStr = getStrPostValue(formData, FormConstants.LOCALE_PARAM);
            Locale locale = Locale.getDefault();
            if (StringUtils.isNotBlank(localeStr)) {
                locale = Locale.forLanguageTag(localeStr);
            }
            FormUtil.genForm(Long.valueOf(formData.get(FORM_ID).toString()), bootProperties.getDomain(), getUrlContext(serverProperties), locale);
            String name = getStrPostValue(formData, "name");
            if (StringUtils.isBlank(name)) {
                return ResultVo.builder().success(false).msg("表单名称不能为空").build();
            }
            SysMenu sysMenu = new SysMenu();
            sysMenu.setCode(name);
            List<SysMenu> resources = SpringUtil.getBean(ResourceService.class).getResources(sysMenu);
            if (CollectionUtils.isEmpty(resources)) {
                return ResultVo.builder().success(true).msg("生成表单成功，由于没有创建资源，无法同步权限标识").build();
            }
            ResourceFormHandleAction resourceFormHandle = SpringUtil.getBean(ResourceFormHandleAction.class);
            Map<String, Object> params = Map.of("code", name, "pId", resources.getFirst().getId());
            resourceFormHandle.batImportAndUpdatePerms(params);
        } catch (Exception e) {
            msg = e.getMessage();
            log.error("生成表单配置数据失败", e);
            return ResultVo.builder().success(false).msg(msg).build();
        }
        return ResultVo.builder().success(true).msg(msg).build();
    }

    @Override
    public AmisTableData<FormProperties> listFormInfo(FormQuery formQuery) {
        List<FormProperties> list = formRepo.listFormInfo(formQuery);
        AmisTableData<FormProperties> amisTableData = new AmisTableData<>();
        amisTableData.setRows(list);
        return amisTableData;
    }

    @Override
    public Select formTypes() {
        Select select = new Select();
        select.setOptions(formRepo.listFormType().stream().map(type -> {
            Select.Option option = new Select.Option();
            option.setLabel(type);
            option.setValue(type);
            return option;
        }).collect(Collectors.toList()));
        return select;
    }

    @Override
    public Select formOptions() {
        Select select = new Select();
        select.setOptions(formRepo.listFormInfo(new FormQuery()).stream().map(info -> {
            Select.Option option = new Select.Option();
            option.setLabel(info.getName());
            option.setValue(info.getFormId().toString());
            return option;
        }).collect(Collectors.toList()));
        return select;
    }

    @Override
    public boolean updateFormPro(FormProDto formProDto) throws Exception {
        if (StringUtils.isBlank(formProDto.getIds())) {
            return false;
        }
        String[] ids = formProDto.getIds().split(",");
        for (String id : ids) {
            Long formId = Long.valueOf(id);
            FormDef formDef = formRepo.getFormDef(formId);
            if (formDef == null) {
                throw new BusinessException("无法获取表单定义,formId = " + formId);
            }
            log.info("开始更新表单属性,formId = {}", formId);
            formProDto.setFormName(formProDto.getFormName());
            String content = formDef.updateFormContent(formRepo.getFormRawContentById(formId), formProDto);
            FormDefDto formDefDto = new FormDefDto();
            formDefDto.setContent(content);
            formDefDto.setId(formId);
            formDefDto.setName(formDef.getName());
            formDefDto.setType(formDef.getType());
            formRepo.updateFormDef(formDefDto);
            log.info("更新表单属性结束,formId = {}", formId);
        }
        return true;
    }

    @Override
    public FormI18nConfig getI18nConfig() {
        URL baseUrl = getClass().getClassLoader().getResource("i18n");
        if (baseUrl == null) {
            throw new BusinessException("无法获取i18n配置文件");
        }
        try {
            FormI18nConfig formI18nConfig = json2Object(FileUtils.readFileToString(new File(baseUrl.getFile(), I18N_CONFIG_FILE), StandardCharsets.UTF_8), FormI18nConfig.class);
            if (formI18nConfig == null) {
                throw new BusinessException("无法获取i18n配置文件");
            }
            try (Stream<Path> paths = Files.list(Paths.get(baseUrl.toURI())).filter(path -> path.toFile().getName().endsWith("properties"))) {
                paths.forEach(path -> {
                    String fileName = path.toFile().getName().substring(0, path.toFile().getName().lastIndexOf('.'));
                    if (Objects.equals("messages", fileName)) {
                        return;
                    }
                    String[] parts = fileName.split("_");
                    String languageCode = parts[1];
                    try {
                        Field field = FormI18nConfig.class.getDeclaredField(languageCode);
                        field.setAccessible(true);
                        field.set(formI18nConfig, readPropertiesFile(path.toFile()));
                    } catch (Exception e) {
                        log.error("无法获取i18n配置文件", e);
                        throw new BusinessException("无法获取国际化配置文件");
                    }
                });
            } catch (URISyntaxException e) {
                log.error("无法获取i18n配置文件", e);
                throw new BusinessException("无法获取国际化配置文件");
            }
            return formI18nConfig;
        } catch (IOException e) {
            log.error("无法获取i18n配置文件", e);
            throw new BusinessException("无法获取国际化配置文件");
        }
    }

    public String readPropertiesFile(File file) {
        Properties properties = new Properties();
        StringBuilder sb = new StringBuilder();
        try (InputStream input = new FileInputStream(file)) {
            properties.load(new InputStreamReader(input, StandardCharsets.UTF_8));
            properties.forEach((key, value) -> sb.append(key).append(" = ").append(value).append("\n"));
        } catch (Exception e) {
            log.error("Properties文件读取失败", e);
        }
        return sb.toString();
    }

    @Override
    public boolean updateI18nConfig(FormI18nConfig formI18nConfig) {
        List<String> group = formI18nConfig.getGroup();
        if (group != null) {
            URL baseUrl = getClass().getClassLoader().getResource("i18n");
            if (baseUrl == null) {
                return false;
            }
            try {
                FormI18nConfig copy = new FormI18nConfig();
                copy.setGroup(group);
                FileUtils.writeStringToFile(new File(baseUrl.getFile(), I18N_CONFIG_FILE), object2json(copy), StandardCharsets.UTF_8);
                Arrays.stream(FormI18nConfig.class.getDeclaredFields()).forEach(field -> {
                    field.setAccessible(true);
                    try {
                        Object value = field.get(formI18nConfig);
                        if (value != null) {
                            File targetFile = getI18nFile(field.getName(), baseUrl);
                            if (targetFile != null) {
                                FileUtils.writeStringToFile(targetFile, (String) value, StandardCharsets.UTF_8);
                            }
                            if (field.getName().equals("zh")) {
                                FileUtils.writeStringToFile(new File(baseUrl.getFile(), "messages.properties"), (String) value, StandardCharsets.UTF_8);
                            }
                        }
                    } catch (Exception e) {
                        throw new RuntimeException("无法获取数据，字段 = " + field.getName());
                    }
                });
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return true;
    }

    private File getI18nFile(String languageCode, URL baseUrl) throws Exception {
        try (Stream<Path> paths = Files.list(Paths.get(baseUrl.toURI())).filter(path -> path.toFile().getName().endsWith("properties"))) {
            return paths.map(Path::toFile).filter(file -> file.getName().contains(languageCode)).findFirst().orElse(null);
        }
    }

    @Override
    public FormListVo<?> getFormList(FormListQuery formListQuery) {
        long start = System.currentTimeMillis();
        FormDef formDef = getFormDef(formListQuery.getFormId());
        FormDataHandle formDataHandle = getFormHandle(formDef, formListQuery.getFormId());
        FormListVo<?> ret = (FormListVo<?>) ((DsInvoke) () -> formDataHandle.query(formDef, formListQuery)).invoke1(formListQuery.getSearchData());
        log.info("查询表单数据耗时 = {}", (System.currentTimeMillis() - start) + "ms");
        return ret;
    }

    @Override
    public Paging<?> page(FormListQuery formListQuery) throws Exception {
        FormDef formDef = getFormDef(formListQuery.getFormId());
        FormDataHandle formDataHandle = getFormHandle(formDef, formListQuery.getFormId());
        return (Paging<?>) ((DsInvoke) () -> formDataHandle.page(formDef, formListQuery)).invoke(formListQuery.getSearchPostData());
    }

    private FormDataHandle getFormHandle(FormDef formDef, Long formId) {
        if (formDef == null) {
            throw new BusinessException("表单定义不存在 formId = " + formId);
        }
        String formType = formDef.getType();
        FormDataHandle formDataHandle = formHandles.get(formType);

        if (formDataHandle == null) {
            formDataHandle = formHandles.get(DEFAULT_FORM_TYPE);
        }
        if (formDataHandle == null) {
            throw new BusinessException("无法获取表单处理程序,formType = " + formType);
        }
        return formDataHandle;
    }

    private FormDataHandle getFormHandle(String formType) {
        FormDataHandle formDataHandle = formHandles.get(formType);
        if (formDataHandle == null) {
            throw new BusinessException("无法获取表单处理程序,formType = " + formType);
        }
        return formDataHandle;
    }

    interface DsInvoke {
        default Object invoke(Map<String, Object> param) throws Exception {
            return invokeInner(param.get("ds"));
        }

        default Object invoke1(Map<String, String[]> param) {
            String ds = null;
            String[] value = param.get("ds");
            if (ArrayUtils.isNotEmpty(value)) {
                ds = value[0];
            }
            return invokeInner(ds);
        }

        default Object invokeInner(Object ds) {
            return DataSourceService.invoke(ds, () -> {
                try {
                    return invoke0();
                } catch (Exception e) {
                    log.error("调用方法失败", e);
                    if (e instanceof InvocationTargetException) {
                        throw new BusinessException(((InvocationTargetException) e).getTargetException().getMessage());
                    }
                    throw new BusinessException(e.getMessage());
                }
            });
        }

        Object invoke0() throws Exception;

    }
}